package org.as3search.criteria.impl {
	/*
	 * as3search - http://code.google.com/p/as3search
	 * Copyright (c) 2009-2010 by respective authors. All rights reserved.
	 *
	 * This library is free software; you can redistribute it and/or modify it under the
	 * terms of the GNU Lesser General Public License as published by the Free Software
	 * Foundation; either version 2.1 of the License, or (at your option) any later
	 * version.
	 *
	 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
	 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
	 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
	 *
	 * You should have received a copy of the GNU Lesser General Public License along
	 * with this library; if not, write to the Free Software Foundation, Inc.,
	 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
	 */
	
	import org.flexunit.Assert;
	
	import org.as3search.config.FlexSearchConfiguration;
	import org.as3search.config.LightSearchConfiguration;
	import org.as3search.config.SearchConfiguration;
	import org.as3search.criteria.Criteria;
	import org.as3search.criteria.Criterion;
	import org.as3search.criteria.EmptyExpression;
	import org.as3search.criteria.EqualityExpression;
	import org.as3search.criteria.Restrictions;
	import org.as3search.criteria.SubCriteria;
	import org.as3search.properties.PropertyResolver;
	import org.as3search.providers.ArraySearchProvider;
	import org.as3search.providers.SearchProvider;
	
	/**
	 * 
	 */
	public class CriteriaImplTestCase {
		
		private var classToTestRef:org.as3search.criteria.impl.CriteriaImpl;
		
		private var provider:SearchProvider;
		
		private var configuration:SearchConfiguration;
		
		public function CriteriaImplTestCase() {
			
		}
		
		[Before]
		public function setup():void {
			provider = new ArraySearchProvider([{a:'b'}]);
			configuration = new LightSearchConfiguration();
			classToTestRef = new CriteriaImpl(provider, configuration);
		}
		
		[Test]
		public function testAdd():void {
//			make sure it throws an error on null
			try {
				classToTestRef.add(null);
				Assert.fail("did not throw an error on passing a null criterion");
			} catch (e:ArgumentError) {
//				pass
			}
//			must not return null
			var expression:EqualityExpression = new EqualityExpression("abcdefg","abcdef");
			Assert.assertNotNull("add() should never return null!",
				classToTestRef.add(expression));
//			test duplicate expressions
			try {
				classToTestRef.add(expression);
				Assert.fail("did not throw an error on duplicate add!");
			} catch (e1:ArgumentError) {
//				pass
			}
//			test for SearchConfigurationAware injection
			var emptyExpression:EmptyExpression = new EmptyExpression("collection", new FlexSearchConfiguration());
			classToTestRef.add(emptyExpression);
			Assert.assertEquals("failed to auto-inject SearchConfiguration into SearchConfigurationAware expression",
				configuration, emptyExpression.configuration);
//			test for PropertyResolverAware injection
			var praCriterion:PropertyResolverAwareCriterion = new PropertyResolverAwareCriterion();
			classToTestRef.add(praCriterion);
			Assert.assertEquals("failed to auto-inject PropertyResolver into PropertyResolverAware expression",
				configuration.propertyResolver, praCriterion.propertyResolver);
//			validate that the results did get added.
			Assert.assertTrue("add() did not add items to the 'criteriaCollection'",
				classToTestRef.criteriaCollection.indexOf(emptyExpression) != -1);
		}
		
		[Test]
		public function testCriteriaImpl():void {
			try {
				new CriteriaImpl(null, null);
				Assert.fail("did not throw an error on null provider/provider.target!");
			} catch (e:ArgumentError) {
//				pass
			}
//			property setting tests
			Assert.assertEquals("failed to set configuration in constructor",
				classToTestRef.configuration, configuration);
			Assert.assertEquals("failed to set provider in constructor",
				classToTestRef.provider, provider);
		}
		
		[Test]
		public function testList():void {
//			never null! rawr!
			Assert.assertNotNull("list should never return null",
				classToTestRef.list());
//			simple matching.
			var collection:Array = [{id: 0}, {id: 1},
				{id: 2}, {id :3}, {id: 4}, {id: 5}, {id: 6}];
			provider = new ArraySearchProvider(collection);
			classToTestRef = new CriteriaImpl(provider, configuration);
			var result:Array = classToTestRef.add(Restrictions.lt("id", 4))
				.list();
			Assert.assertNotNull("failed to return a non-null value with one criterion",
				result);
			Assert.assertEquals("failed to return an expected amount of items",
				4, result.length);
//			advanced matching
			collection = [{id: 1, friends: [{id: 3}]}, 
				{id: 2, friends: [{id: 1}]},
				{id: 3, friends: [{id: 2},{id: 1}]},
				{id: 4, friends: [{id: 1}]},
				{id: 5, friends: [{id: 2}]}];
			provider = new ArraySearchProvider(collection);
			classToTestRef = new CriteriaImpl(provider, configuration);
			result = classToTestRef.add(Restrictions.ne("id", 2))
				.createCriteria("friends", Restrictions.eq("id", 2))
				.list();
			Assert.assertNotNull("failed to return a non-null value with one criterion " +
				"and one subcriteria", result);
			Assert.assertEquals("failed to return the expected amount of items with one " +
				"subcriteria.", 2, result.length);
			for each (var item:Object in result) {
//				make sure each item does not have an id of 2
				Assert.assertTrue("failed to return appropriate results (id mismatch)",
					item.id != 2);
//				make sure each item has a friend with id of 2
				var match:Boolean = false;
				for each (var itemFriend:Object in item.friends) {
					if (itemFriend.id == 2) {
						match = true;
						break;
					}
				}
				Assert.assertTrue("failed to return appropriate results (friend.id mismatch)",
					match);
			}
		}
		
		[Test]
		public function testTest():void {
//			test positive
			var collection:Array = [{id: 2, friends: [], name: "ally"}];
			provider = new ArraySearchProvider(collection);
			classToTestRef = new CriteriaImpl(provider, configuration);
			classToTestRef.add(Restrictions.eq("id", 2))
				.add(Restrictions.isEmpty("friends"))
				.add(Restrictions.matches("name", new RegExp("lly$")));
			Assert.assertTrue("failed to evaluate simple matches",
				classToTestRef.test());
//			test negative
			collection = [{id: 2, friends: ['friendly!'], name: "ally"}];
			provider = new ArraySearchProvider(collection);
			classToTestRef = new CriteriaImpl(provider, configuration);
			classToTestRef.add(Restrictions.eq("id", 2))
				.add(Restrictions.isEmpty("friends"))
				.add(Restrictions.matches("name", new RegExp("lly$")));
			Assert.assertFalse("failed to reject non-matches",
				classToTestRef.test());
//			test advanced
			collection = [{id: 1, friends: [{id: 3}]},
				{id: 2, friends: [{id: 1}]},
				{id: 3, friends: [{id: 1}]}];
			provider = new ArraySearchProvider(collection);
			classToTestRef = new CriteriaImpl(provider, configuration);
//			reject
			Assert.assertFalse("failed to reject entries not matching subcriteria",
				classToTestRef.add(Restrictions.ne("id", 2))
					.createCriteria("friends", Restrictions.eq("id", 2))
					.test());
//			keep
			collection = [{id: 1, friends: [{id: 2}]},
				{id: 2, friends: [{id: 1}, {id: 3}]},
				{id: 3, friends: [{id: 2}, {id: 1}]}];
			provider = new ArraySearchProvider(collection);
			classToTestRef = new CriteriaImpl(provider, configuration);
			Assert.assertTrue("failed to select entries matching subcriteria",
				classToTestRef.add(Restrictions.ne("id", 2))
					.createCriteria("friends", Restrictions.eq("id", 2))
					.test());
		}
		
		[Test]
		public function testUniqueResult():void {
			var collection:Array = [{id: 2, friends: [], name: "billy"}];
			provider = new ArraySearchProvider(collection);
			classToTestRef = new CriteriaImpl(provider, configuration);
			classToTestRef.add(Restrictions.eq("id", 2))
				.add(Restrictions.isEmpty("friends"))
				.add(Restrictions.matches("name", new RegExp("lly$")));
//			test what should match
			Assert.assertNotNull("failed to match the given criteria",
				classToTestRef.uniqueResult());
//			test what should NOT match
			collection.shift();
			collection.push({id: 2, friends: [], name: "fatty"});
			Assert.assertNull("failed to return null on something that doesn't match",
				classToTestRef.uniqueResult());
//			test subcriteria for subcollections
			collection = [{id: 1, friends: [{id: 3}]},
				{id: 2, friends: [{id: 1}]},
				{id: 3, friends: [{id: 1}]}];
			provider = new ArraySearchProvider(collection);
			classToTestRef = new CriteriaImpl(provider, configuration);
			Assert.assertNull("failed to reject entries not matching subcriteria",
				classToTestRef.add(Restrictions.ne("id", 2))
					.createCriteria("friends", Restrictions.eq("id", 2))
					.uniqueResult());
			collection = [{id: 1, friends: [{id: 2}]},
				{id: 2, friends: [{id: 1},{id:3}]},
				{id: 3, friends: [{id: 2},{id:1}]}];
			provider = new ArraySearchProvider(collection);
			classToTestRef = new CriteriaImpl(provider, configuration);
			Assert.assertNotNull("failed to select entries matching subcriteria",
				classToTestRef.add(Restrictions.ne("id", 2))
					.createCriteria("friends", Restrictions.eq("id", 2))
					.uniqueResult());
		}
		
		[Test]
		public function testCreateCriteria():void {
			provider = new ArraySearchProvider([{subcollection: [{id: 0}, {id: 1}, {id: 2}]}]);
			classToTestRef = new CriteriaImpl(provider, configuration);
			var subcriterion:Criterion = Restrictions.conjunction()
				.add(Restrictions.lt("id", 5));
			var criteria:Criteria = classToTestRef.createCriteria("subcollection", subcriterion);
			Assert.assertEquals("createCriteria should return the main criteria entry",
				classToTestRef, criteria);
			Assert.assertNotNull("createCriteria did not create a SubCriteria entry",
				classToTestRef.subCriteriaCollection[0]);
			var subcriteria:SubCriteria = SubCriteria(classToTestRef.subCriteriaCollection[0]);
			Assert.assertEquals("createCriteria did not inject config into SubCriteria",
				configuration, subcriteria.configuration);
			Assert.assertEquals("createCriteria did not inject propertyName into SubCriteria",
				"subcollection", subcriteria.propertyName);
			Assert.assertEquals("createCriteria did not inject parent into SubCriteria",
				classToTestRef, subcriteria.parent);
			Assert.assertEquals("createCriteria did not inject criterion into SubCriteria",
				subcriterion, SubCriteriaImpl(subcriteria).criteriaCollection[0]);
		}
		
		[Test]
		public function testCriteriaCollection():void {
			Assert.assertNotNull("'criteriaCollection' should never be null, ever!!!",
				classToTestRef.criteriaCollection);
			classToTestRef.criteriaCollection = null;
			Assert.assertNotNull("failed to set 'criteriaCollection' to [] when set to null",
				classToTestRef.criteriaCollection);
		}
	}
}

import org.as3search.criteria.Criterion;
import org.as3search.properties.PropertyResolver;
import org.as3search.properties.PropertyResolverAware;

class PropertyResolverAwareCriterion implements Criterion, PropertyResolverAware {
	
	private var _propertyResolver:PropertyResolver;
	
	public function PropertyResolverAwareCriterion() {
		
	}
	
	public function evaluate(value:Object):Boolean { 
		return true;
	}
	
	public function get propertyResolver():PropertyResolver { return _propertyResolver; }
	public function set propertyResolver(value:PropertyResolver):void { _propertyResolver = value; }
}